﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SenetExperimentsRunner.Logic.Experiment;
using SenetExperimentsRunner.Logic.Runner;
using System.IO;

namespace SenetExperimentsRunner.Logic.Genetic_Algorithm
{
    class GeneticAlgorithm
    {
        public void doGeneticAlgorithm()
        {
            //Tal's heuristic
            //m_heuristics[0] = new Heuristic();
            //m_heuristics[0].setCoefficient(0, 18);
            //m_heuristics[0].setCoefficient(1, 2);
            //m_heuristics[0].setCoefficient(2, 15);
            //m_heuristics[0].setCoefficient(3, 20);
            //m_heuristics[0].setCoefficient(4, 10);
            //m_heuristics[0].setCoefficient(5, 2);
            //m_heuristics[0].setCoefficient(6, 0);

            //Adam's heuristic
            //m_heuristics[1] = new Heuristic();
            //m_heuristics[1].setCoefficient(0, 13);
            //m_heuristics[1].setCoefficient(1, 10);
            //m_heuristics[1].setCoefficient(2, 2);
            //m_heuristics[1].setCoefficient(3, 13);
            //m_heuristics[1].setCoefficient(4, 8);
            //m_heuristics[1].setCoefficient(5, 2);
            //m_heuristics[1].setCoefficient(6, 7);

            m_heuristics[0] = new Heuristic();
            m_heuristics[0].setCoefficient(0, 18);
            m_heuristics[0].setCoefficient(1, 15);
            m_heuristics[0].setCoefficient(2, 0);
            m_heuristics[0].setCoefficient(3, 0);
            m_heuristics[0].setCoefficient(4, 6);
            m_heuristics[0].setCoefficient(5, 6);
            m_heuristics[0].setCoefficient(6, 10);

            m_heuristics[1] = new Heuristic();
            m_heuristics[1].setCoefficient(0, 18);
            m_heuristics[1].setCoefficient(1, 16);
            m_heuristics[1].setCoefficient(2, 12);
            m_heuristics[1].setCoefficient(3, 5);
            m_heuristics[1].setCoefficient(4, 6);
            m_heuristics[1].setCoefficient(5, 8);
            m_heuristics[1].setCoefficient(6, 10);

            m_heuristics[2] = new Heuristic();
            m_heuristics[2].setCoefficient(0, 18);
            m_heuristics[2].setCoefficient(1, 17);
            m_heuristics[2].setCoefficient(2, 8);
            m_heuristics[2].setCoefficient(3, 5);
            m_heuristics[2].setCoefficient(4, 6);
            m_heuristics[2].setCoefficient(5, 8);
            m_heuristics[2].setCoefficient(6, 10);

            m_heuristics[3] = new Heuristic();
            m_heuristics[3].setCoefficient(0, 11);
            m_heuristics[3].setCoefficient(1, 15);
            m_heuristics[3].setCoefficient(2, 0);
            m_heuristics[3].setCoefficient(3, 5);
            m_heuristics[3].setCoefficient(4, 16);
            m_heuristics[3].setCoefficient(5, 6);
            m_heuristics[3].setCoefficient(6, 10);

            m_heuristics[4] = new Heuristic();
            m_heuristics[4].setCoefficient(0, 12);
            m_heuristics[4].setCoefficient(1, 8);
            m_heuristics[4].setCoefficient(2, 7);
            m_heuristics[4].setCoefficient(3, 0);
            m_heuristics[4].setCoefficient(4, 19);
            m_heuristics[4].setCoefficient(5, 5);
            m_heuristics[4].setCoefficient(6, 5);

            


            
                //TODO: determine the first 2 heuristics manually
            //for (int i = 2; i < HEURISTICS_NUM; ++i)
            //{
            //    m_heuristics[i] = new Heuristic();
            //    CreateRandomParams(m_heuristics[i]);
            //}
            m_winnerLog.WriteLine("****************** STARTING GENETIC ALGORITHM... *******************************");
            for (int i = 0; i < ITERATION_NUMBER; ++i)
            {
                List<Experiment.Experiment> tournament = createTournament(m_heuristics);
                runTournament(tournament);
            }



        }

        #region Private functions

        /// <summary>
        /// run tournament and change m_heuristic to hold 5 new players for next tournament
        /// </summary>
        /// <param name="tournament"></param>
        private void runTournament(List<Experiment.Experiment> tournament)
        {
            m_experimentRunner.RunExperiments(tournament);
            
            //tournament results[i] holds number of wins for player i
            int[] tournamentResults = new int[HEURISTICS_NUM];
            
            //collect tournament results
            countTournanamentResults(ref tournamentResults, tournament);
            
            //order the results. tournamentResult[0] holds winning player
            orderTournamentResults(ref tournamentResults);

            printToLogs(tournamentResults);

            //create new players for next round
            createPlayersForNextRound(tournamentResults);         
        }

        

        private void createPlayersForNextRound(int[] tournamentResults)
        {
            Heuristic[] nextPlayers = new Heuristic[HEURISTICS_NUM];
            
            //first player is previous round winner
            nextPlayers[0] = m_heuristics[tournamentResults[0]];

            //for players 1..(last-1):
            //player i is a combination of previous round locations i-1, i
            for (int i = 1; i < HEURISTICS_NUM - 1; ++i)
            {
                nextPlayers[i] = crateNewPlayer(m_heuristics[tournamentResults[i - 1]], m_heuristics[tournamentResults[i]]);
            }

            //last player is entirely new
            nextPlayers[HEURISTICS_NUM - 1] = new Heuristic();
            CreateRandomParams(nextPlayers[HEURISTICS_NUM-1]);

            //copy results to m_heuristics
            for (int i = 0; i < HEURISTICS_NUM; ++i)
            {
                m_heuristics[i] = nextPlayers[i];
            }
        }

        private void printToLogs(int[] results)
        {
            int winningPlayerIndex = results[0];
            //print to winners log
            m_winnerLog.WriteLine("Winner:\t" + m_heuristics[winningPlayerIndex].ToString());
            //print to full log
            ++m_iterationPassed;
            m_fullLog.WriteLine("**************** Iteration number: " + m_iterationPassed.ToString() + "************************");
            for (int i = 0; i < HEURISTICS_NUM; ++i)
            {
                m_fullLog.WriteLine("place " + i.ToString() + ": " + m_heuristics[results[i]].ToString());
            }
            m_fullLog.Flush();
            m_winnerLog.Flush();
        }

        private Heuristic crateNewPlayer(Heuristic player1, Heuristic player2)
        {
            Heuristic newPlayer = new Heuristic();
            for (int i = 0; i < Heuristic.SUB_HEURISTICS_NUM; ++i)
            {
                //selecting which parameter to  take is done by flipping a coin
                int selection = m_random.Next(2);
                if(selection == 0)
                {
                    newPlayer.setCoefficient(i, player1.getCoefficient(i));
                }
                else
                {
                    newPlayer.setCoefficient(i, player2.getCoefficient(i));
                }     
            }
            return newPlayer;
        }

        private void orderTournamentResults(ref int[] tournamentResults)
        {

            int[] indexes = new int[tournamentResults.Length];
            for (int i = 0; i < tournamentResults.Length; ++i)
            {
                indexes[i] = i;
            }
            Array.Sort(tournamentResults, indexes);
            int j = 0;
            for (int i = tournamentResults.Length - 1; i >= 0; --i)
            { 
                tournamentResults[j++] = indexes[i];
            }
        }

        private void countTournanamentResults(ref int[] tournamentResults, List<Experiment.Experiment> tournament)
        {
            for (int i = 0; i < HEURISTICS_NUM; ++i)
            {
                tournamentResults[i] = 0;
            }

            //count tournament results
            foreach (Experiment.Experiment experiment in tournament)
            {
                int player1 = -1, player2 = -1;
                getPlayersNames(ref player1, ref player2, experiment);
                if (experiment.ExperimentResults.PercentagePlayer1Win > experiment.ExperimentResults.PercentagePlayer2Win)
                {
                    ++tournamentResults[player1];
                }
                else
                {
                    ++tournamentResults[player2];
                }
            }
        }

        private void getPlayersNames(ref int player1, ref int player2, Experiment.Experiment experiment)
        {
            string experimentName = experiment.ExperimentParams.ExperimentName;
            string player1Name = experimentName.Substring("player: ".Length, 1);
            string player2Name = experimentName.Substring("player: i VS. player :".Length, 1);
            try
            {
                player1 = Convert.ToInt32(player1Name);
                player2 = Convert.ToInt32(player2Name);
            }
            catch (Exception e)
            {
                throw new Exception("GeneticAlgorithm.getPlayersNames MSG: error parsing names");
            }
        }

        private List<Experiment.Experiment> createTournament(Heuristic[] players)
        {
            List<Experiment.Experiment> tournament = new List<Experiment.Experiment>();
            
            //each player play agains all other players
            for (int i = 0; i < HEURISTICS_NUM; ++i)
            {
                for (int j = i + 1; j < HEURISTICS_NUM; ++j)
                {
                    // i vs. j
                    AddExperimentToTournament(i, j, tournament);

                    // j vs. i
                    AddExperimentToTournament(j, i, tournament);             
                }
            }

            return tournament;
        }

        private void AddExperimentToTournament(int player1, int player2, List<Experiment.Experiment> tournament)
        {
            Experiment.Experiment experiment = new Experiment.Experiment();
            SetExperimentParams(experiment, player1, m_heuristics[player1], player2, m_heuristics[player2]);
            tournament.Add(experiment);

        }

        private void SetExperimentParams(Experiment.Experiment experiment, int firstPlayer, Heuristic p1Heuristic, 
                                        int secondPlayer, Heuristic p2Heuristic)
        {
            Experiment.ExperimentParams parameters = new Experiment.ExperimentParams();
            parameters.ExperimentId = firstPlayer + secondPlayer;
            parameters.ExperimentName = "player: " + firstPlayer.ToString() + " VS. player: " + secondPlayer.ToString();
            parameters.TrialsNumber = TRIALS_PER_MATCH_NUM;
            
            //game parameters
            parameters.GameParameters.AdditionalRebirthSquares = 2;
            parameters.GameParameters.AdditionalSafeSquares = 2;
            parameters.GameParameters.InitialPlayerPieces = 5;
            parameters.GameParameters.PlayerTurnNumber = 100;
            parameters.GameParameters.SecondsPerTurn = SECONDS_PER_TURN;
            parameters.GameParameters.SquaresNumber = 30;
            parameters.GameParameters.SticksNumber = 6;

            
            //player 1
            parameters.Player1Params.PlayerType = SenetPlayerParams.SenetPlayerType.ExpectimaxHeuristic;
            p1Heuristic.Coefficients.CopyTo(parameters.Player1Params.HeuristicsParams.Coefficients, 0);
            
            //player 2
            parameters.Player2Params.PlayerType = SenetPlayerParams.SenetPlayerType.ExpectimaxHeuristic;
            p2Heuristic.Coefficients.CopyTo(parameters.Player2Params.HeuristicsParams.Coefficients, 0);

            experiment.ExperimentParams = parameters;
        }

        //create random parameters for heuristic
        private void CreateRandomParams(Heuristic  heuristic)
        {
            
            int[] coefficients = new int[Heuristic.SUB_HEURISTICS_NUM];
            
            for (int i = 0; i < Heuristic.SUB_HEURISTICS_NUM; ++i)
            {
                heuristic.setCoefficient(i,m_rand.Next(21));
            }
        }
        /// <summary>
        /// normalize coefficent so that linear combination will add to 1
        /// </summary>
        /// <param name="heuristic"></param>
        /// <param name="coefficients"></param>
        //private void NormalizeCoefficients(Heuristic heuristic, int[] coefficients)
        //{
        //    //float sum = 0;
        //    ////sum all coefficients
        //    //for (int i = 0; i < Heuristic.SUB_HEURISTICS_NUM; ++i)
        //    //{
        //    //    sum += coefficients[i];
        //    //}
            
        //    ////actual coefficiant are relative coefficient weight out of sum:
        //    //for (int i = 0; i < Heuristic.SUB_HEURISTICS_NUM; ++i)
        //    //{
        //    //    heuristic.setCoefficient(i, (((float)coefficients[i]) / sum));
        //    //}
        //}

        #endregion Private Functions

        #region Members

        private Heuristic[] m_heuristics = new Heuristic[HEURISTICS_NUM];
        private ExperimentRunner m_experimentRunner = new ExperimentRunner();
        private Random m_random = new Random();
        private TextWriter m_winnerLog = new StreamWriter(winnerLogPath);
        private TextWriter m_fullLog = new StreamWriter(fullLogPath);
        private int m_iterationPassed = 0;
        private Random m_rand = new Random();
        #endregion

        #region Constants

        //number of algorithm iterations
        private const int ITERATION_NUMBER = 4;

        //number of heuristic in a single tournament
        private const int HEURISTICS_NUM = 5;
        //number of game each 2 players play
        private const int TRIALS_PER_MATCH_NUM = 1;
        //seconds per turn
        private const double SECONDS_PER_TURN = 1;

        //log
        private const string winnerLogPath = @".\..\..\..\GeneticLogs\Genetic_Algorithm_Winner_Log.txt";
        private const string fullLogPath = @".\..\..\..\GeneticLogs\Genetic_Algorithm_Full_Log.txt";

        #endregion Constants
    
    }
}
